(function(win){
  function sameVnode(vnode1, vnode2) {
    return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel
  }
  function isTextVnode(vnode) {
    return vnode.text != undefined &&
      (vnode.children == undefined || vnode.children.length == 0)
  }
  function hasChildren(vnode) {
    return Array.isArray(vnode.children) && vnode.children.length != 0
  }
  /**
   * @description 插入data
   * @param {Element} node 
   * @param {Object} data 
   */
  function addData(node, data) {
    const props = data.props
    if (props) {
      for (let key in props) {
        node.setAttribute(key, props[key])
      }
    }
    const classlist = data.classlist
    if (classlist) {
      for (let key in classlist) {
        if (classlist[key]) {
          node.classList.add(key)
        }
      }
    }
  }
  /**
   * @description return an object
   * @param {String} sel 
   * @param {any} data 
   * @param {Array<Vnode|String>} children 
   * @param {String} text 
   * @param {Element|String} elm 
   */
  function vnode (sel, data, children, text, elm) {
    const key = data.key
    return { sel, data, children, text, elm, key }
  }

  /**
   * @description 用于产生虚拟节点
   * @param {String} sel 
   * @param {Object} data 
   * @param {Array<any>|Vnode|String} c 
   * h(ele, {}, '');
   * h(ele, {}, Vnode);
   * h(ele, {}, [...Vnodes]};
   */
  function h(sel, data, c) {
    if (arguments.length !== 3) {
      throw new Error('传入h函数的必须是三个参数')
    }
    if (typeof c === 'string' || typeof c === 'number') {
      return vnode(sel, data, undefined, c, undefined)
    } else if (Array.isArray(c)) {
      let children = []
      for (let i = 0, len = c.length; i < len; i++) {
        let it = c[i];
        if (typeof it != 'object' || !it.hasOwnProperty('sel')) {
          throw new Error('第三个参数必须是对象数组');
        }
        children.push(it);
      }
      return vnode(sel, data, children, undefined, undefined)
    } else if (typeof c === 'object' && c.hasOwnProperty('sel')) {s
      let children = [c];
      return vnode(sel, data, children, undefined, undefined);
    } else {
      throw new Error('传入h函数的值不合法'); 
    }
  }

  /**
   * @description 创建一个真正的DOM节点
   * @param {Object} vnode 
   */
  function createElem(vnode) {
    let domnode = document.createElement(vnode.sel)
    if (vnode.text != '' && (vnode.children == undefined || vnode.children.length == 0)) {
      domnode.innerText = vnode.text
    }
    else if (hasChildren(vnode)) {
      for (let i = 0, len = vnode.children.length; i < len; ++i) {
        const ch = vnode.children[i]
        domnode.appendChild(createElem(ch))
      }
    }
    vnode.elm = domnode
    addData(vnode.elm, vnode.data)
  
    return vnode.elm
  }

  function patchVnode(oldVnode, newVnode) {
    if (oldVnode == newVnode) {
      return;
    }
    if (isTextVnode(newVnode)){
      if (newVnode.text != oldVnode.text) {
        oldVnode.elm.innerText = newVnode.text
        addData(oldVnode.elm, newVnode.data)
      }

      newVnode.elm = oldVnode.elm

    } else {
      if (!hasChildren(oldVnode)) {
        oldVnode.elm.innerHTML = ''
        let subdom
        for (let i = 0, len = newVnode.children.length; i < len; ++i) {
          subdom = createElem(newVnode.children[i])
          oldVnode.elm.appendChild(subdom)
        }
        
        newVnode.elm = oldVnode.elm
        addData(newVnode.elm, newVnode.data)
  
      } else {
        updataChildren(oldVnode.elm, oldVnode.children, newVnode.children)
      }
    }
  }

  function updataChildren (parentElm, oldCh, newCh) {
    let oldStartIdx = 0, oldEndIdx = oldCh.length - 1,
        newStartIdx = 0, newEndIdx = newCh.length - 1,
        oldStartVnode = oldCh[oldStartIdx], oldEndVnode = oldCh[oldEndIdx],
        newStartVnode = newCh[newStartIdx], newEndVnode = newCh[newEndIdx]
  
    let keymap = null
  
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (!oldStartVnode) {
        oldStartVnode = oldCh[++oldStartIdx]
      } else if (!oldEndVnode) {
        oldEndVnode = oldCh[--oldEndIdx]
      } if (!newStartVnode) {
        newStartVnode = newCh[++newStartIdx]
      } else if (!newEndVnode) {
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(newStartVnode, oldStartVnode)) {
        patchVnode(oldStartVnode, newStartVnode)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(newEndVnode, oldEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(newEndVnode, oldStartVnode)) {
        patchVnode(oldStartVnode, newEndVnode)
        parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling) 
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) {
        patchVnode(oldEndVnode, newStartVnode)
        parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        if (!keymap) {
          keymap = {}
          for (let i = oldStartIdx; i < oldEndIdx + 1; ++i) {
            const key = oldCh[i].key
            if (key) {
              keymap[key] = i
            }
          }
        }
        const idxInOld = keymap[newStartVnode.key]
  
        if (!idxInOld) {
          parentElm.insertBefore(createElem(newStartVnode), oldStartVnode.elm)
        } else {
          const elmToMove = oldCh[idxInOld]
          patchVnode(elmToMove, newStartVnode)
          oldCh[idxInOld] = undefined
          parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm)
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
  
    if (newStartIdx <= newEndIdx) {
      const pivot = newCh[newEndIdx+1] ? newCh[newEndIdx+1].elm : null
      for (let i = newStartIdx; i < newEndIdx + 1; ++i) {
        parentElm.insertBefore(createElem(newCh[i]), pivot)
      }
    } else if (oldStartIdx <= oldEndIdx) {
      for (let i = oldStartIdx; i < oldEndIdx + 1; ++i) {
        if (oldCh[i]) {
          parentElm.removeChild(oldCh[i].elm)
          // 标记已处理
          oldCh[i] = undefined
        }
      }
    }
  }

  function patch(oldVnode, newVnode) {
    if (oldVnode.sel == '' || oldVnode.sel == undefined) {
      oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode)
    }
    if (sameVnode(oldVnode, newVnode)) {
      patchVnode(oldVnode, newVnode)
    } else {
      if (oldVnode.elm.parentNode) {
        const domnode = createElem(newVnode)
        oldVnode.elm.parentNode.insertBefore(domnode, oldVnode.elm)
        oldVnode.elm.parentNode.removeChild(oldVnode.elm)
      }
    }
  }

  window.snabApi = {
    h, patch
  }
})(window)